Skip to content

fix(cli): error messages in commands/ (14 commands + their tests)#1255

Merged
John-David Dalton (jdalton) merged 8 commits intomainfrom
jdd/error-msg-commands
Apr 24, 2026
Merged

fix(cli): error messages in commands/ (14 commands + their tests)#1255
John-David Dalton (jdalton) merged 8 commits intomainfrom
jdd/error-msg-commands

Conversation

@jdalton
Copy link
Copy Markdown
Contributor

@jdalton John-David Dalton (jdalton) commented Apr 22, 2026

Summary

Align error messages across 14 command modules with the 4-ingredient strategy (what / where / saw / fix) defined in CLAUDE.md (landed via #1254). This is the biggest of the error-message batches — 27 files total (14 sources + 13 tests + lockfile).

Scope

Sources in packages/cli/src/commands/:

  • ask/cmd-ask.mts
  • audit-log/cmd-audit-log.mts
  • config/handle-config-set.mts
  • fix/cmd-fix.mts, fix/coana-fix.mts
  • login/cmd-login.mts
  • manifest/convert-gradle-to-maven.mts
  • organization/cmd-organization-dependencies.mts
  • scan/cmd-scan-create.mts, scan/cmd-scan-list.mts, scan/cmd-scan-reach.mts
  • threat-feed/cmd-threat-feed.mts
  • wrapper/add-socket-wrapper.mts, wrapper/postinstall-wrapper.mts

Plus matching integration + unit test updates.

Related PRs (sibling error-message batches)

Test plan

  • pnpm --filter @socketsecurity/cli run type clean
  • pnpm --filter @socketsecurity/cli run lint clean
  • Unit + integration tests for affected commands pass

Rewrites error messages across packages/cli/src/commands/ to follow
the What / Where / Saw vs. wanted / Fix strategy from CLAUDE.md's
new Error Messages section.

Sources:
- scan/cmd-scan-create.mts: 5 throws (ecosystems + 4 numeric flags)
- scan/cmd-scan-reach.mts: 4 throws (ecosystems + 3 numeric flags)
- scan/cmd-scan-list.mts: 2 throws (--page, --per-page)
- organization/cmd-organization-dependencies.mts: 2 throws (--limit, --offset)
- audit-log/cmd-audit-log.mts: 2 throws (--page, --per-page)
- threat-feed/cmd-threat-feed.mts: 1 throw (--per-page)
- fix/cmd-fix.mts: 1 logger.fail (--ecosystems)
- ask/cmd-ask.mts: 1 InputError (missing QUERY)
- login/cmd-login.mts: 1 InputError (non-interactive TTY)
- wrapper/add-socket-wrapper.mts: 1 throw (fs.appendFile failure)
- wrapper/postinstall-wrapper.mts: 1 throw (nested wrapper setup)
- manifest/convert-gradle-to-maven.mts: 1 throw (spawn returned no output)
- fix/coana-fix.mts: 1 throw (coana returned non-array JSON)
- config/handle-config-set.mts: 1 throw (missing VALUE)

Tests updated to match new substrings (regex patterns for easier
future evolution).

All throws previously typed as plain Error that represent user input
validation have been converted to InputError, following the codebase
convention.

Follows strategy landed in #1254.
Switch `(e as Error).message` to `e instanceof Error ? e.message : String(e)` so that when a non-Error value is thrown (strings, objects, null) the error message stays informative instead of becoming 'undefined'.

Same fix as applied to #1260 (iocraft.mts) after Cursor bugbot flagged the pattern on that PR.
Switch the inline `e instanceof Error ? e.message : String(e)` to
getErrorCause(e), matching the fleet helper pattern. Same behavior
for Error throws; non-Error throws now produce 'Unknown error'
instead of '[object Object]'.
@jdalton
Copy link
Copy Markdown
Contributor Author

bugbot run

Comment thread packages/cli/src/commands/wrapper/add-socket-wrapper.mts
Cursor bugbot flagged both add-socket-wrapper.mts and
postinstall-wrapper.mts for rendering I/O errors with the "Invalid
input" title and "Check command syntax with --help" recovery hint
(InputError's display mapping). fs.appendFile failures are permission /
disk / path problems — FileSystemError renders them as "File system
error" with contextual recovery (check file permissions, disk space,
etc.).

Pass the file path (where available) and the ErrnoException code
through so FileSystemError can surface EACCES/ENOSPC/ENOENT-specific
recovery text.

Reported on PR #1255.
The previous commit swapped InputError → FileSystemError in
postinstall-wrapper.mts but left the test's vi.mock factory still
exporting InputError, so the source couldn't resolve its
FileSystemError import under vitest:

  [vitest] No "FileSystemError" export is defined on the
  ".../src/utils/error/errors.mts" mock

Replace the mock's InputError stub with a FileSystemError stub that
matches the real class's shape (path, code, recovery) so the source
code resolves correctly. The existing regex assertion on the error
message is unaffected.
@jdalton
Copy link
Copy Markdown
Contributor Author

bugbot run

Comment thread packages/cli/src/commands/wrapper/add-socket-wrapper.mts
Comment thread packages/cli/src/commands/scan/cmd-scan-create.mts
Two issues flagged on the fresh review of HEAD:

- FileSystemError duplicates the filepath: the message embeds ${file}
  AND the constructor receives `file` as the path argument. Since
  display.formatErrorForDisplay appends \`(\${error.path})\` automatically,
  this surfaced the path twice. Drop the \${file} interpolation from
  the message — the path arg alone is enough.

- Scan command numeric flag errors overstated validation: \`--pull-request\`
  claimed "non-negative integer" and \`--reach-concurrency\` claimed
  "positive integer," but the checks only rejected NaN. Negatives and
  floats passed through silently. Tighten the validation to match what
  the error messages promise — Number.isInteger + range check at every
  call site (cmd-scan-create.mts for both flags, cmd-scan-reach.mts for
  reach-concurrency).

The third flagged item (FileSystemError vs InputError in the wrapper
commands) was cursor re-flagging a finding already addressed in commit
769170f — the current code already uses FileSystemError. No action.
@jdalton
Copy link
Copy Markdown
Contributor Author

bugbot run

Copy link
Copy Markdown

@cursor cursor Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cursor Bugbot has reviewed your changes and found 1 potential issue.

Fix All in Cursor

Bugbot Autofix is ON. A cloud agent has been kicked off to fix the reported issue.

Comment @cursor review or bugbot run to trigger another review on this PR

Reviewed by Cursor Bugbot for commit 2269113. Configure here.

Comment thread packages/cli/test/unit/commands/wrapper/add-socket-wrapper.test.mts Outdated
Previous commit (2269113) dropped the \${file} interpolation from
FileSystemError's message to avoid display.formatErrorForDisplay
double-printing the path (which it appends from error.path). The test
regex still matched the pre-change format — /failed to append socket
aliases to \/etc\/protected-file/ — and wouldn't have matched the new
message.

Assert on the new shape instead:
  - regex matches `failed to append socket aliases (Permission denied)`
  - toMatchObject pins name='FileSystemError' and path='/etc/protected-file'

Drive-by: the existing regex had \./etc\/protected-file/ but the
message never contained that substring after the FileSystemError swap
— so the assertion was silently wrong. Flagged by cursor on #1255.
@jdalton

This comment was marked as resolved.

John-David Dalton (jdalton) added a commit that referenced this pull request Apr 24, 2026
* fix(cli): align test/ error messages with 4-ingredient strategy

Rewrites the Socket-JSON contract validator and auth-flow mocks under
packages/cli/src/test/ and packages/cli/test/ to follow the What /
Where / Saw vs. wanted / Fix strategy from CLAUDE.md.

Sources:
- src/test/json-output-validation.mts (6 throws): each violation now
  spells out the full Socket-JSON contract, the received value, and
  a concrete fix ("add ok:true", "return empty object", etc.).
  Long output payloads are truncated to 200 chars in the message so
  errors stay readable.
- src/test/mocks/socket-auth.mts (2 throws): "Authentication failed"
  and "OAuth timeout" now call out that they come from a test
  fixture and point at the configuration flag to change.
- test/json-output-validation.mts (2 non-throwing returns): message
  values now include the exit code / parse error and a stdout
  preview so failing tests diagnose themselves.
- test/smoke.sh (6 labels): updated to mirror the TS validator so
  the bash and JS harnesses produce the same wording.

Tests: full suite (343 files / 5225 tests) still passes. No
assertions touched — the unrelated "Authentication failed" hits
in other tests are test fixtures constructing their own Errors,
not references to the mock.

Follows strategy from #1254. Continues #1255-#1258.

* chore(cli): harden (e as Error) casts to safe stringify

Switch `(e as Error).message` to `e instanceof Error ? e.message : String(e)` so that when a non-Error value is thrown (strings, objects, null) the error message stays informative instead of becoming 'undefined'.

Same fix as applied to #1260 (iocraft.mts) after Cursor bugbot flagged the pattern on that PR.
@jdalton John-David Dalton (jdalton) changed the title fix(cli): align commands/ error messages with 4-ingredient strategy fix(cli): error messages in commands/ (14 commands + their tests) Apr 24, 2026
@jdalton John-David Dalton (jdalton) merged commit c2b1df9 into main Apr 24, 2026
13 checks passed
@jdalton John-David Dalton (jdalton) deleted the jdd/error-msg-commands branch April 24, 2026 19:50
John-David Dalton (jdalton) added a commit that referenced this pull request Apr 24, 2026
* fix(cli): align utils/dlx/ error messages with 4-ingredient strategy

Rewrites error messages across packages/cli/src/utils/dlx/ to follow
the What / Where / Saw vs. wanted / Fix strategy from CLAUDE.md.

Sources:
- spawn.mts: 27 messages
  - 6x 'Unexpected resolution type for <tool>' — now name the resolver
    function and the actual resolution.type seen
  - Archive/platform errors name the supported formats/platforms
  - Python DLX errors surface the lock file path and cache dir
  - PyPI fetch errors include the URL that failed
  - Security errors (zip-slip, symlink escape) tell user to delete
    the cached asset and report upstream
- resolve-binary.mts: 4 messages (socket-patch, trivy, trufflehog,
  opengrep platform support) — each now lists supported platforms
  and suggests how to install the tool manually
- vfs-extract.mts: 5 messages (SEA VFS extraction failures) — each
  names what went wrong with the bundle and how to recover
  (usually: rebuild SEA)

Internal invariant errors stay as plain Error (not InputError) but
are informative enough that if they ever fire, the user can open
a useful bug report.

Tests updated: test/unit/utils/dlx/resolve-binary.test.mts (1
substring match switched to regex).

Follows strategy from #1254. Part of the multi-PR series started
by #1255 (commands/).

* fix(cli): hoist lockFile/pythonDir above retry check in ensurePythonDlx

The previous commit referenced `${lockFile}` and `${pythonDir}` in
the MAX_RETRIES error message, but those consts were declared AFTER
the retry check. Hitting the retry path threw ReferenceError from
the temporal dead zone instead of the intended InputError.

Fix: move the three const declarations (pythonDir, pythonBin,
lockFile) above the MAX_RETRIES guard. Caught by Cursor bugbot
(#1256 (comment))
and confirmed by the type-check job.

* chore(cli): harden (e as Error) casts to safe stringify

Switch `(e as Error).message` to `e instanceof Error ? e.message : String(e)` so that when a non-Error value is thrown (strings, objects, null) the error message stays informative instead of becoming 'undefined'.

Same fix as applied to #1260 (iocraft.mts) after Cursor bugbot flagged the pattern on that PR.

* chore(cli): use joinAnd + getErrorCause helpers in dlx error messages

Switch to shared fleet helpers so error lists render as human prose
('a, b, and c') and error-cause stringification works safely for
non-Error throws (falls back to 'Unknown error' instead of crashing
or producing 'undefined').

- resolve-binary.mts: SOCKET_PATCH_ASSETS + OPENGREP_ASSETS
  platform lists now use `joinAnd(Object.keys(...))`.
- vfs-extract.mts: missingTools list uses joinAnd; extract-failure
  error now uses getErrorCause(e) — equivalent to the inline
  'e instanceof Error ? e.message : String(e)' with the standard
  UNKNOWN_ERROR fallback.
- spawn.mts: output-map listing uses joinAnd.

No behavior change for Error throws; non-Error throws now produce
'Unknown error' instead of '[object Object]' or similar.
John-David Dalton (jdalton) added a commit that referenced this pull request Apr 24, 2026
…rary migration (#1257)

* fix(cli): align utils/update/ + utils/command/ error messages with 4-ingredient strategy

Rewrites error messages across packages/cli/src/utils/update/ and
packages/cli/src/utils/command/ to follow the What / Where /
Saw vs. wanted / Fix strategy from CLAUDE.md.

Sources:
- utils/update/checker.mts: 8 messages (URL validation, package name
  / registry URL validation, version-response validation). Each now
  names the function, the received type, and what a valid value
  looks like.
- utils/update/manager.mts: 3 messages (mirror guards for name /
  version / ttl). Still warn-and-return-false, but the text now
  tells the caller exactly which option was wrong.
- utils/command/registry-core.mts: 6 messages (command / alias
  registration conflicts, middleware next() misuse, flag parsing
  failures). Each now names the offending command, flag name, or
  index so debuggers don't need to read source.

Tests updated:
- test/unit/utils/update/checker.test.mts: 6 assertions (switched
  to regex)
- test/unit/utils/update/manager.test.mts: 3 assertions (switched
  to expect.stringContaining)
- test/unit/utils/command/registry-core.test.mts: 5 assertions

All 153 tests in the affected suites pass.

Follows strategy from #1254. Part of the multi-PR series started
by #1255 (commands/) and continued in #1256 (utils/dlx/).

* fix(cli): address Cursor bugbot findings on error messages

Two issues flagged by Cursor bugbot on #1257:

1. (Medium) registry-core.mts middleware error reported the wrong
   offending middleware index. `index` tracks the highest dispatched
   position, not the caller; when dispatch(i) is re-entered after a
   double-next(), the offender held middleware[i - 1]'s next closure.
   Fixed to use `i - 1` with a comment explaining why.

2. (Low) checker.mts error referenced a non-existent
   `UpdateChecker.fetch()` — the object is actually exported as
   `NetworkUtils`. Renamed in both the error and its test regex.

Caught by #1257 bugbot review.

* docs(claude): align Error Messages with fleet doctrine, add references doc

Restructure the CLI-specific Error Messages section to match the
updated fleet doctrine from socket-repo-template:

- Keep the four ingredients (What / Where / Saw vs. wanted / Fix).
- Add audience-based length guidance (library API terse / CLI verbose /
  programmatic rule-only). `InputError`/`AuthError` usages are
  verbose-tier.
- Tighten baseline rules to one-liners; drop narrative phrasing.
- Preserve the CLI-specific examples (--pull-request, socket init,
  AuthError) — they earn their keep as real anti-pattern fodder.

Add `docs/references/error-messages.md` with fleet-wide worked examples
so CLAUDE.md stays tight and the rich anti-patterns live once.

* docs(claude): reference joinAnd / joinOr helpers in Error Messages

Point readers at @socketsecurity/lib/arrays' list-formatting helpers
from CLAUDE.md (one-line rule) and the worked-examples references doc
(new "Formatting lists of values" section).

* chore: bump @socketsecurity/lib to 5.24.0 and adopt error helpers

Fleet-wide migration to the caught-value helpers in
@socketsecurity/lib/errors.

- pnpm-workspace.yaml: catalog bump 5.21.0 → 5.24.0.
- 18 src files under packages/cli/src: replace every
  `e instanceof Error ? e.message : String(e)` and `UNKNOWN_ERROR`
  fallback with `errorMessage(e)`; replace bare `x instanceof Error`
  boolean checks with `isError(x)`; replace
  `e instanceof Error ? e.stack : undefined` with `errorStack(e)`.
- packages/cli/src/utils/error/errors.mts: drop the locally-defined
  `isErrnoException` (which accepted any `code !== undefined`) and
  re-export the library's stricter string-code variant.
- packages/cli/src/commands/manifest/convert-{gradle,sbt}-to-maven.mts:
  rename a local `const errorMessage` → `summary` to free the
  identifier for the imported helper.
- packages/cli/src/utils/telemetry/service.mts: rename two local
  `const errorMessage = …` variables to `errMsg` inside catch
  blocks for the same reason.
- docs/references/error-messages.md: pick up the new "Working with
  caught values" section from socket-repo-template.

Out of scope (intentionally left):
- Exit-code checks on child-process results (`'code' in e` where
  `code` is a number, e.g. display.mts:257). `isErrnoException`
  requires a string code and would wrongly return false.
- The local `getErrorMessage` / `getErrorMessageOr` helpers in
  errors.mts — callers outside this file still use them; a
  broader refactor to the library `errorMessage` is follow-up.

Pre-commit tests skipped (DISABLE_PRECOMMIT_TEST); `pnpm run type`
and `pnpm run lint` pass.

* fix(cli): stop duplicating cause messages in display.mts loop

Cursor bugbot flagged the while(currentCause) loop in displayError: it
walks the .cause chain manually but was calling errorMessage() on each
level, which itself walks the entire remaining chain via
messageWithCauses. For A → B → C, that printed "B msg: C msg" at depth
1, then "C msg" at depth 2, showing C's message twice.

Switch to reading `.message` directly (matching the pre-PR behavior
the bot pointed to) so each iteration emits only that level's text.
Fall back to `String(currentCause)` for non-Error nodes in the chain.

Drop the now-unused `errorMessage` import.

Reported on PR #1261.

* test(cli): update debug.test for @socketsecurity/lib/errors 5.24 semantics

The debugApiResponse test expected errorMessage('String error') to
return the 'Unknown error' sentinel, matching the old local shim's
behavior that treated any non-Error caught value as unusable. The
catalog bump to @socketsecurity/lib 5.24 switched debug.mts to the
upstream errorMessage, which preserves non-empty primitives as-is —
only empty strings, null, undefined, and plain objects coerce to the
sentinel. Assert on 'String error' to pin the current contract.
John-David Dalton (jdalton) added a commit that referenced this pull request Apr 24, 2026
…, terminal) (#1260)

* fix(cli): align utils/ miscellaneous error messages with 4-ingredient strategy

Final PR in the error-message series. Covers everything not already
touched by #1255-#1259: utils/basics, utils/config, utils/fs,
utils/git, utils/npm, utils/promise, utils/terminal, and the flags
module at the root of the CLI tree.

Sources:
- flags.mts: 2 throws (--max-old-space-size, --max-semi-space-size)
  — name the flag, show the offending value, suggest a concrete
  megabyte value.
- utils/config.mts: 1 throw (SOCKET_CLI_CONFIG base64 decode) —
  explains the replacement-character symptom and how to re-encode.
- utils/basics/vfs-extract.mts: 4 throws (SEA VFS extraction for
  Python + security tools) — name the missing paths, the exit
  codes, and point at the "rebuild the SEA binary" fix.
- utils/promise/queue.mts: 1 throw (PromiseQueue concurrency guard)
  — show the offending value and suggest 4/8.
- utils/npm/spec.mts: 1 throw (PURL conversion) — show the input,
  state what a valid npm spec looks like.
- utils/git/operations.mts: 1 throw (git-not-on-PATH) — point at
  install and the local-path env-var override.
- utils/git/gitlab-provider.mts: 2 throws (no token, PR creation
  after retries) — name the token scope, the retry count, the
  repo/head refs.
- utils/fs/path-resolve.mts: 1 throw (npm path-walk iteration cap)
  — name the start path, current directory, and what usually
  causes the cycle (symlinks).
- utils/terminal/iocraft.mts: 1 throw (native-module load failure)
  — show the underlying error and the offending platform/arch
  triple.

Skipped (already informative):
- github-provider.mts pass-through errors (forward inner CResult
  cause/message)
- gitlab-provider.mts try/catch wrappers that call
  formatErrorWithDetail (inner error has context)
- 'process.exit called' sentinel throws in npm/pnpm/yarn/with-
  subcommands paths (test harness re-raise markers, not user-facing)

Tests updated:
- test/unit/utils/promise/queue.test.mts (2 assertions)
- test/unit/utils/npm/spec.test.mts (2 assertions)
- test/unit/utils/git/gitlab-provider.test.mts (3 assertions)

Full suite (343 files / 5225 tests) passes.

Completes the series: #1255 (commands/) → #1256 (utils/dlx/) →
#1257 (utils/update + utils/command/) → #1258 (env/ + constants/) →
#1259 (test/) → this.

* fix(cli): address Cursor bugbot findings on error messages

Four issues flagged by Cursor bugbot on #1260:

1. (Medium) gitlab-provider.mts: error said 'check GL_TOKEN permissions'
   but the actual env var is GITLAB_TOKEN (as the same file's getGitLabToken
   confirms). Fixed to GITLAB_TOKEN.

2. (Medium) git/operations.mts: error suggested 'set SOCKET_CLI_GIT_PATH
   to point at a specific binary' — that env var is not read anywhere.
   Removed the false suggestion; kept the real fix (install git and put
   it on PATH) with package-manager examples.

3. (Low) terminal/iocraft.mts: '(e as Error).message' evaluates to
   undefined when a non-Error is thrown. Switched to
   'e instanceof Error ? e.message : String(e)' for safe stringification.

4. (Low) gitlab-provider.mts: error said 'after ${retries} retries' but
   the loop runs attempt 1..retries inclusive — retries is the total
   attempt count, not retries beyond the first. Reworded to 'attempts'.
   Matching test assertions updated.

Caught by #1260 bugbot review.

* chore(cli): use joinAnd + getErrorCause helpers in utils/ misc

- basics/vfs-extract.mts: missingTools list now renders as prose
  via joinAnd('a, b, and c').
- terminal/iocraft.mts: inline `e instanceof Error ? e.message :
  String(e)` swapped for getErrorCause(e). require() of a native
  binding can throw non-Error values, so the safe-stringify with
  UNKNOWN_ERROR fallback is correct here.

No behavior change for Error throws.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants